#include "SocketCompat.h"
#include <iostream>
#include "SocketClient.h"

#define SC_DEBUG_VERBOSE 0

/**************************************************
SocketClient::SocketClient

Instantiates the Socket Client. Fixes the port to
which the returned object can connect. That is,
it can connect to any machine on a fixed port
decided at instantiation time.

Optional arguments:
user_read_timeout_secs	Number of seconds before reads time out.
Default value is 1 second.
user_read_timeout_usecs	Number of microseconds before reads time out.
Total timeout is (secs + (usecs / 1,000,000)).
Default value is 0 microseconds.
user_write_timeout_secs	Number of seconds before writes time out.
Default value is 1 second.
user_write_timeout_usecs	Number of microseconds before writes time out.
Total timeout is (secs + (usecs / 1,000,000)).
Default value is 0 microseconds.
user_quiet			If nonzero, suppresses all printed output from
the object.
user_sys_v_signals		A flag used internally to the SocketMan structure
telling the system whether to reinstall the
SIGPIPE signal handler after it is called.
Should be 1 if your system supports only Sys V
signals (i.e. HP/UX). Default value is 0.
(For SGIs, should be 0.)
user_use_multicast		If nonzero, turns on multicast mode.
Default value is 0.
user_suppress_multicast_loop	If zero, all packets written using multicast
mode will be looped back to the host machine.
Default value is 1.
char *initial_server_name	If not NULL, makes an initial connection to
this server upon instantiation.
Default value is NULL.
*************************************************/

SocketClient::SocketClient(unsigned short target_port,
						   int user_read_timeout_secs,
						   int user_read_timeout_usecs,
						   int user_write_timeout_secs,
						   int user_write_timeout_usecs,
						   int user_quiet,
						   int user_sys_v_signals,
						   int user_use_multicast,
						   int user_suppress_multicast_loop,
						   char *initial_server_name)
{
	port = target_port;
	read_timeout.tv_sec = user_read_timeout_secs;
	read_timeout.tv_usec = user_read_timeout_usecs;
	write_timeout.tv_sec = user_write_timeout_secs;
	write_timeout.tv_usec = user_write_timeout_usecs;
	read_timeout_float = (float) user_read_timeout_secs + (float) user_read_timeout_usecs * (float) (1.0e-6);
	write_timeout_float = (float) user_write_timeout_secs + (float) user_write_timeout_usecs * (float) (1.0e-6);

	current_socket = new SocketStruct;
	quiet = user_quiet;
	global_sm_sys_v_signals = user_sys_v_signals;
	use_multicast = user_use_multicast;
	suppress_multicast_loop = user_suppress_multicast_loop;
#ifndef WIN32
	signal(SIGPIPE, handleSIGPIPE);
#endif

	// Create the multicast address subscription request if necessary.
#ifdef SM_HAVE_MULTICAST
	multicast_address = (void *) new ip_mreq;
#else
	multicast_address = NULL;
#endif

	if (initial_server_name)
	{
		// User expects server connection to be made.
		// Block on making it. I don't like this, but
		// I've gotten slammed for not including it.

		int temp;

		while ((temp = connectToServer(initial_server_name)) != MSG_NO_ERROR)
		{
			if (!quiet)
			{
				cerr << "SocketClient::SocketClient: error making initial connection" << endl;
			}
			Sleep(1);
		}
	}
}

/**************************************************
SocketClient::~SocketClient

Destructor. Closes the current connection if
necessary and cleans up memory usage.
*************************************************/

SocketClient::~SocketClient()
{
	if (current_socket->fd > 0)
		closeConnection();
	delete current_socket;
#ifdef SM_HAVE_MULTICAST
	if (multicast_address)
		delete ((ip_mreq *) multicast_address);
#endif
}

/**************************************************
SocketClient::allowMulticastLoopback ** PROTECTED

Allows loopback of multicast messages.
*************************************************/

int
SocketClient::allowMulticastLoopback()
{
	char loop = 1;
#ifdef SM_HAVE_MULTICAST
	if (setsockopt(current_socket->fd, IPPROTO_IP,
		IP_MULTICAST_LOOP, (char *) &loop, sizeof(loop)) < 0)
	{
		cerr << "SocketClient::allowMulticastLoopback: Error allowing loop back on multicast socket" << endl;
		return -1;
	}
	return 0;
#else
	cerr << "SocketClient::allowMulticastLoopback: multicasting not enabled" << endl;
	return -1;
#endif
}

/**************************************************
SocketClient::suppressMulticastLoopback PROTECTED

Suppresses loopback of multicast messages.
*************************************************/

int
SocketClient::suppressMulticastLoopback()
{
	char loop = 0;
#ifdef SM_HAVE_MULTICAST
	if (setsockopt(current_socket->fd, IPPROTO_IP,
		IP_MULTICAST_LOOP, (char *) &loop, sizeof(loop)) < 0)
	{
		cerr << "SocketClient::suppressMulticastLoopback: Error suppressing loop back on multicast socket" << endl;
		return -1;
	}
	return 0;
#else
	cerr << "SocketClient::suppressMulticastLoopback: multicasting not enabled" << endl;
	return -1;
#endif
}

/**************************************************
SocketClient::connectToServer
Makes a new socket that connects to the server.
Returns MSG_NO_ERROR if successful.

Errors:
ERR_UNKNOWN_HOST
ERR_CREATING_SOCKET
ERR_CONNECTING_TO_SERVER
ERR_UDP_CREATING_SOCKET
ERR_UDP_BINDING_SOCKET
ERR_UDP_NO_SERVER_INFORMATION
*************************************************/

int
SocketClient::connectToServer(const char *server_name,
							  char *interface_name)
{
	int ret;
	struct sockaddr_in server;
	struct hostent     *host_info;

#ifndef SC_TEST_DEBUG
	if (current_socket->fd > 0)
	{
		if (!quiet)
		{
			cerr << "SocketClient::connectToServer: already connected" << endl;
		}
		global_message = ERR_ALREADY_CONNECTED_TO_SERVER;
		return global_message;
	}
#endif

	memset((char *)&server, 0, sizeof(server));

	if (use_multicast)
	{
#ifdef SM_HAVE_MULTICAST
		server.sin_addr.s_addr = inet_addr(server_name);
		if (server.sin_addr.s_addr == INADDR_NONE)
		{
			if (!quiet)
			{
				cerr << "SocketClient::connectToServer: bad multicast address " <<
					server_name << endl;
			}
			global_message = ERR_UNKNOWN_HOST;
			return global_message;
		}
		server.sin_family = AF_INET;
#else /* SM_HAVE_MULTICAST */
		cerr << "SocketClient: multicast support not defined in Makefile" << endl;
#endif
	}
	else
	{
		host_info = (struct hostent *)gethostbyname((char *)server_name);
		if (host_info == NULL)
		{  
			global_message = ERR_UNKNOWN_HOST;
			return global_message;
		}
		memcpy((char *)&(server.sin_addr), host_info->h_addr, host_info->h_length);
		server.sin_family = host_info->h_addrtype;
	}

	server.sin_port = htons(port);

	if (use_multicast)
	{
#ifdef SM_HAVE_MULTICAST


		if (!IN_MULTICAST(ntohl(server.sin_addr.s_addr)))
		{
			if (!quiet)
				cerr << "SocketClient::connectToServer: invalid multicast address" << endl;
			global_message = ERR_MULTICAST_INVALID_ADDRESS;
			return global_message;
		}

		// make a local copy of the address for sends
		multicast_group_address.sin_port = server.sin_port;
		multicast_group_address.sin_addr.s_addr = server.sin_addr.s_addr;
		multicast_group_address.sin_family = server.sin_family;

		// make the socket we'll be using for multicast input
		if ((current_socket->fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
		{
			current_socket->fd = 0;
			global_message = ERR_MULTICAST_CREATING_SOCKET;
			return global_message;
		}

		// get the IP address of our network interface
		struct ifreq interface_address;
		if (interface_name)
		{
			strcpy(interface_address.ifr_name, interface_name);
			if (ioctlsocket (current_socket->fd, SIOCGIFADDR, &interface_address) < 0)
			{
				closesocket(current_socket->fd);
				current_socket->fd = 0;
				if (!quiet)
					cerr << "SocketClient::connectToServer: error getting network interface" << endl;
				global_message = ERR_MULTICAST_GETTING_NETWORK_INTERFACE;
				return global_message;
			}
		}
		else
		{
			// Try the interface names we know about

			int succeeded = 0;
			char *known_interface_names[] = {"ec0",
				"et0",
				"ef0",
				"lan0",
				"eth0",
				"le0",
				"ln0",
				"lis0",
				NULL};

			for (char **if_name = known_interface_names; *if_name && (!succeeded); if_name++)
			{
				strcpy(interface_address.ifr_name, *if_name);
				if (ioctlsocket(current_socket->fd, SIOCGIFADDR, &interface_address) >= 0)
					succeeded = 1;
			}

			if (!succeeded)
			{
				closesocket(current_socket->fd);
				current_socket->fd = 0;
				if (!quiet)
					cerr << "SocketClient::connectToServer: error getting network interface" << endl;
				global_message = ERR_MULTICAST_GETTING_NETWORK_INTERFACE;
				return global_message;
			}	      
		}

		// set the IP address of the group
		((struct ip_mreq *) multicast_address)->imr_multiaddr.s_addr = server.sin_addr.s_addr;
		// set the IP address of the network interface
		struct sockaddr_in *temp_interface_address;
		temp_interface_address = (sockaddr_in *) &interface_address.ifr_addr;
		((struct ip_mreq *) multicast_address)->imr_interface.s_addr = temp_interface_address->sin_addr.s_addr;
		// The following line is commented out,
		// even though it appeared to work.
		// I wouldn't recommend not getting the network interface address
		//      multicast_address.imr_interface.s_addr = htonl(INADDR_ANY);

		// allow multiple clients to bind to the same port
		int one = 1;
		if (setsockopt(current_socket->fd, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one)) == -1)
		{
			if (!quiet)
				cerr << "SocketClient::connectToServer: error setting REUSEADDR" << endl;
			closesocket(current_socket->fd);
			current_socket->fd = 0;
			global_message = ERR_MULTICAST_REUSING_ADDRESS;
			return global_message;
		}
#ifdef SO_REUSEPORT
		if (setsockopt(current_socket->fd, SOL_SOCKET, SO_REUSEPORT, (char *)&one, sizeof(one)) == -1)
		{
			if (!quiet)
				cerr << "SocketClient::connectToServer: error setting REUSEADDR" << endl;
			closesocket(current_socket->fd);
			current_socket->fd = 0;
			global_message = ERR_MULTICAST_REUSING_ADDRESS;
			return global_message;
		}
#endif

		// bind the socket to the desired port
		// NOTE! according to testing, binding to our *address*
		// as well as port will cause this procedure to FAIL!
		// bind to INADDR_ANY instead.

		struct sockaddr_in client_info;
		memset((char *)&client_info, 0, sizeof(client_info));
		client_info.sin_family = AF_INET;
		client_info.sin_port = htons(port);
		client_info.sin_addr.s_addr = htonl(INADDR_ANY);

		ret = bind(current_socket->fd, (struct sockaddr *) &client_info, sizeof(client_info));
		if (ret == -1)
		{  
			closesocket(current_socket->fd);
			current_socket->fd = 0;
			if (!quiet)
				cerr << "SocketClient::connectToServer: Error binding multicast socket to port" << endl;
			global_message = ERR_MULTICAST_BINDING_SOCKET;
			return global_message;
		}

		// make the membership request
		if ((setsockopt(current_socket->fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
			multicast_address, sizeof(struct ip_mreq))) == -1)
		{
			closesocket(current_socket->fd);
			current_socket->fd = 0;
			if (!quiet)
			{
				cerr << "SocketClient::connectToServer: error making membership request: " << flush;
				perror("");
			}
			global_message = ERR_MULTICAST_SUBSCRIBING_TO_GROUP;
			return global_message;
		}

		// set loop back
		if (suppress_multicast_loop)
			suppressMulticastLoopback();

		// enable non blocking IO for the multicast socket

#ifndef WIN32
		int flags;
		flags = fcntl(current_socket->fd, F_GETFL, 0);
		flags |= O_NDELAY;
		fcntl(current_socket->fd, F_SETFL, flags);
#else
		unsigned long nb_arg = 1;
		ioctlsocket(current_socket->fd, FIONBIO, &arg);
#endif

		// we're set up
		use_udp = 1;
		global_message = MSG_NO_ERROR;
		return global_message;
#else /* SM_HAVE_MULTICAST */
		cerr << "SocketClient: multicast support not defined in Makefile" << endl;
#endif /* SM_HAVE_MULTICAST */
	}

	if ((current_socket->fd = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
	{
		current_socket->fd = 0;
		global_message = ERR_CREATING_SOCKET;
		return global_message;
	}

	ret = connect(current_socket->fd, (struct sockaddr *)(&server), sizeof(server));
	if (ret == -1)
	{
		closesocket(current_socket->fd);
		current_socket->fd = 0;
		global_message = ERR_CONNECTING_TO_SERVER;
		return global_message;
	}

#ifndef WIN32
	// enable non blocking IO
	int flags;
	flags = fcntl(current_socket->fd, F_GETFL, 0);
	flags |= O_NDELAY;
	fcntl(current_socket->fd, F_SETFL, flags);
#else
	unsigned long nb_arg = 1;
	ioctlsocket(current_socket->fd, FIONBIO, &nb_arg);
#endif

	// We have to find out whether the server's using UDP.

	SocketBuf *sbuf;
	SocketStruct tmp;
	struct timeval timeout = {2, 0};
	sbuf = _readWithPollAndTimeout(current_socket, timeout, 2.0, 0);

	if (sbuf)
	{
		unsigned short udp_port;

		if (udp_port = *((unsigned short *) sbuf->buf))
		{
			use_udp = 1;

			// Server just sent back his UDP port number.
			// Make a UDP socket

			int udp_sock;
			udp_sock = socket(AF_INET, SOCK_DGRAM, 0);
			if (udp_sock == INVALID_SOCKET)
			{
				if (!quiet)
					cerr << "SocketClient::connectToServer: error creating UDP socket" << endl;
				global_message = ERR_UDP_CREATING_SOCKET;
				return global_message;
			}

			// get our name so we can bind the
			// socket to our local port

			char                myname[SB_MAXHOSTNAME];
			int                 client_len;
			struct hostent      *my_info;
			struct sockaddr_in  client_info;

			client_len = sizeof(client_info);

			gethostname(myname, SB_MAXHOSTNAME);

			my_info = (struct hostent *)gethostbyname(myname);
			if (my_info == NULL)
			{  
				if (!quiet)
					cerr << "I don't know my own name: \"" << myname << "\"" << endl;
				global_message = ERR_UNKNOWN_HOST;
				return global_message;
			}

			memset((char *)&client_info, 0, client_len);
			memcpy((char *)&(client_info.sin_addr),
				my_info->h_addr,
				my_info->h_length);
			client_info.sin_family = my_info->h_addrtype;
			client_info.sin_port = htons(port);   // using same port number in UDP space
			// that we would use in TCP space

			// bind the UDP socket to our local port

			ret = bind(udp_sock, (struct sockaddr *) &client_info, client_len);
			if (ret == -1)
			{  
				closesocket(udp_sock);
				if (!quiet)
					cerr << "Error binding UDP socket to port" << endl;
				global_message = ERR_UDP_BINDING_SOCKET;
				return global_message;
			}

			// connect to the server, knowing his port from above

			server.sin_port = udp_port;   // already in network byte order
#if SC_DEBUG_VERBOSE
			cerr << "Connecting to port " << ntohs(udp_port) << endl;
#endif
			if (connect(udp_sock, (struct sockaddr *)&server, sizeof(server)) != 0)
			{
				if (!quiet)
					cerr << "SocketClient::connectToServer: UDP connect failed" << endl;
				global_message = ERR_UDP_CONNECTING_SOCKET;
				return global_message;
			}

			/*	  // the server has sent us some information.
			// make sure we can read it, and send some back

			SocketBuf *sbufp;
			SocketStruct tmp;
			struct timeval poll_timeout = {2, 0};

			tmp.fd = udp_sock;
			sbufp = _readWithPollAndTimeout(&tmp, poll_timeout, 2.0, 1);

			if (!sbufp)
			{
			if (!quiet)
			{
			cerr << "UDP server failed to respond" << endl;
			}
			closesocket(udp_sock);
			closesocket(current_socket->fd);
			current_socket->fd = 0;
			global_message = ERR_UDP_SERVER_NO_RESPONSE;
			return global_message;
			}

			// got the data. Now send it back

			_writeWithTimeout(&tmp, sbufp, 2.0, 1);

			*/
			// We're set up. Close the TCP socket

			closesocket(current_socket->fd);

			// enable non blocking IO for the UDP socket

			current_socket->fd = udp_sock;
#ifndef WIN32
			int flags;
			flags = fcntl(current_socket->fd, F_GETFL, 0);
			flags |= O_NDELAY;
			fcntl(current_socket->fd, F_SETFL, flags);
#else
			unsigned long nb_arg = 1;
			ioctlsocket(current_socket->fd, FIONBIO, &nb_arg);
#endif
		}
		else
		{
			// Server isn't using UDP, so we're fine.
			use_udp = 0;
		}
	}
	else
	{
		// Didn't get any information back from the server; assume it died

		closesocket(current_socket->fd);
		current_socket->fd = 0;
		if (!quiet)
		{
			cerr << "Server didn't send UDP information" << endl;
		}
		global_message = ERR_UDP_NO_SERVER_INFORMATION;
		return global_message;
	}

	if (!quiet)
		cerr << "SocketClient::connectToServer: Connected successfully" << endl;
	global_message = MSG_NO_ERROR;
	return global_message;
}

/**************************************************
SocketClient::readWithPollAndTimeout

Polls the current socket and, if ready to read,
attempts to read a packet from it.

If successful, returns a pointer to a SocketBuf
and sets global_message and last_message to
MSG_NO_ERROR. If there was nothing to read,
returns NULL and sets global_message and
last_message to MSG_SOCKET_NOT_READY_TO_READ.
If the read timed out, returns NULL and returns
the appropriate error shown below.
recoverFromReadError may then be called to try
to finish the read.

Errors stored in last_message:
ERR_DURING_SELECT
ERR_UDP_RECEIVE_FAILED
ERR_TIMEOUT_READING_PACKET_LENGTH
ERR_TIMEOUT_READING_PACKET_DATA

Errors stored ONLY in global_message:
ERR_CURRENT_SOCKET_NOT_OPEN
ERR_CURRENT_SOCKET_CLOSED
ERR_SIGPIPE_READING_PACKET_LENGTH
ERR_SIGPIPE_READING_PACKET
*************************************************/

SocketBuf*
SocketClient::readWithPollAndTimeout()
{
	struct timeval timeout = {0, 0};
	SocketBuf* retval;

	global_sc = (void *) this;
	global_sm_or_sc = SC_GLOBAL_SWITCH;
	global_sigpipe_raised = 0;

	retval = _readWithPollAndTimeout(current_socket,
		timeout,
		read_timeout_float,
		use_udp,
		use_multicast);

	if (retval == NULL && global_message == ERR_CURRENT_SOCKET_CLOSED)
	{
		if (!quiet)
		{
			cerr << "Closing it" << endl;
		}
		closeConnection();
	}

	global_sc = NULL;

	return retval;
}

/**************************************************
SocketClient::readWithTimeout

Attempts to read data (without polling) from the
current socket for the number of seconds 
specified by the user in the read-timeout
options in the constructor.

If successful, returns a pointer to a SocketBuf
and sets global_message and last_message to
MSG_NO_ERROR. If there was nothing to read,
returns NULL and sets global_message and
last_message to MSG_SOCKET_NOT_READY_TO_READ.
If the read timed out, returns NULL and returns
the appropriate error shown below.
recoverFromReadError may then be called to try
to finish the read.

Errors stored in last_message:
ERR_DURING_SELECT
ERR_UDP_RECEIVE_FAILED
ERR_TIMEOUT_READING_PACKET_LENGTH
ERR_TIMEOUT_READING_PACKET_DATA

Errors stored ONLY in global_message:
ERR_CURRENT_SOCKET_NOT_OPEN
ERR_CURRENT_SOCKET_CLOSED
ERR_SIGPIPE_READING_PACKET_LENGTH
ERR_SIGPIPE_READING_PACKET
*************************************************/

SocketBuf*
SocketClient::readWithTimeout()
{
	SocketBuf* retval;

	global_sc = (void *) this;
	global_sm_or_sc = SC_GLOBAL_SWITCH;
	global_sigpipe_raised = 0;

	retval = _readWithPollAndTimeout(current_socket,
		read_timeout,
		read_timeout_float,
		use_udp,
		use_multicast);

	if (retval == NULL && global_message == ERR_CURRENT_SOCKET_CLOSED)
	{
		if (!quiet)
		{
			cerr << "Closing it" << endl;
		}
		closeConnection();
	}

	global_sc = NULL;

	return retval;
}

/**************************************************
SocketClient::recoverFromReadError

If the previous read on the current socket timed
out, attempts to finish it by polling to see if
new data has come in and reading it if it has.

If successful, returns a pointer to a SocketBuf
and sets global_message and last_message to
MSG_NO_ERROR. If there was nothing to read,
returns NULL and sets global_message and
last_message to MSG_SOCKET_NOT_READY_TO_READ.
If the read timed out, returns NULL and returns
the appropriate error shown below.
recoverFromReadError may then be called to try
to finish the read.

NOTE: Only works for TCP sockets.

Errors stored in last_message:
ERR_DURING_SELECT
ERR_TIMEOUT_READING_PACKET_LENGTH
ERR_TIMEOUT_READING_PACKET_DATA

Errors stored ONLY in global_message:
ERR_CURRENT_SOCKET_NOT_OPEN
ERR_CURRENT_SOCKET_CLOSED
ERR_SIGPIPE_READING_PACKET_LENGTH
ERR_SIGPIPE_READING_PACKET
*************************************************/

SocketBuf*
SocketClient::recoverFromReadError()
{
	SocketBuf *retval;

	global_sc = (void *) this;
	global_sm_or_sc = SC_GLOBAL_SWITCH;
	global_sigpipe_raised = 0;

	retval = _recoverFromReadError(current_socket,
		read_timeout_float);

	if (retval == NULL && global_message == ERR_CURRENT_SOCKET_CLOSED)
	{
		if (!quiet)
		{
			cerr << "Closing it" << endl;
		}
		closeConnection();
	}

	global_sc = NULL;

	return retval;
}

/**************************************************
SocketClient::writeWithTimeout

Attempts to write the data stored in the 
SocketBuf pointed to by sbuf onto the current
socket. Times out if there are contiguous errors
for the period specified in the write-timeout
sections of the constructor.

Returns MSG_NO_ERROR if successful.

Errors:
ERR_CURRENT_SOCKET_NOT_OPEN
ERR_UDP_PACKET_TOO_LARGE
ERR_UDP_SEND_FAILED
ERR_TIMEOUT_WRITING_PACKET
ERR_SIGPIPE_WRITING_PACKET
*************************************************/

int
SocketClient::writeWithTimeout(SocketBuf *sbuf)
{
	int retval;

	global_sc = (void *) this;
	global_sm_or_sc = SC_GLOBAL_SWITCH;
	global_sigpipe_raised = 0;

	if (use_multicast)
	{
		retval = _writeWithTimeout(current_socket, sbuf, write_timeout_float, use_udp,
			use_multicast, (struct sockaddr *) &multicast_group_address);
	}
	else
		retval = _writeWithTimeout(current_socket, sbuf, write_timeout_float, use_udp);

	global_sc = NULL;

	return retval;
}

/**************************************************
SocketClient::closeConnection

Closes the connection to the server.
Returns MSG_NO_ERROR if successful.

Errors:
ERR_UNSUBSCRIBING_FROM_MULTICAST_GROUP
(only if using multicast mode and connected
to multicast group)
ERR_SHUTTING_DOWN_CONNECTED_SOCKET
ERR_CLOSING_CONNECTED_SOCKET
*************************************************/

int
SocketClient::closeConnection()
{
	if (current_socket->fd)
	{
		if (use_multicast)
		{
#ifdef SM_HAVE_MULTICAST
			// unsubscribe from multicast group
			if (setsockopt(current_socket->fd, IPPROTO_IP, IP_DROP_MEMBERSHIP,
				multicast_address, sizeof(struct ip_mreq)) == -1)
			{
				if (!quiet)
					cerr << "SocketClient::closeConnection: error unsubscribing from multicast group" << endl;
				global_message = ERR_UNSUBSCRIBING_FROM_MULTICAST_GROUP;
				return global_message;
			}
#endif
		}

		if (shutdown(current_socket->fd, 2) == -1)
		{
			if (!quiet)
				cerr << "SocketClient::closeConnection: error shutting down socket " << current_socket->fd << endl;
			global_message = ERR_SHUTTING_DOWN_CONNECTED_SOCKET;
			return global_message;
		}

		if (closesocket(current_socket->fd) == -1)
		{
			if (!quiet)
				cerr << "SocketMan::closeConnectedSockets: error closing socket " << current_socket->fd << endl;
			global_message = ERR_CLOSING_CONNECTED_SOCKET;
			return global_message;
		}

		current_socket->fd = 0;
		global_message = MSG_NO_ERROR;
		return global_message;
	}
	return -1;
}

/**************************************************
SocketClient::getLastMessage

Returns the last message (from the last 
operation executed).
*************************************************/

int
SocketClient::getLastMessage()
{
	return current_socket->last_message;
}

/**************************************************
SocketClient::serverUsingUDP

Returns 1 if the server is using UDP, 0 otherwise.
*************************************************/

int
SocketClient::serverUsingUDP()
{
	return (use_udp ? 1 : 0);
}

/**************************************************
SocketClient::getUsingMulticast

Returns 1 if the SocketClient is in multicast
mode, 0 otherwise.
*************************************************/

int
SocketClient::getUsingMulticast()
{
	return (use_multicast ? 1 : 0);
}

/**************************************************
SocketClient::setUsingMulticast

If the argument to this method is nonzero, turns
on multicast mode. Otherwise turns it off.
If the SocketClient is in a connected state,
switching this parameter breaks the connection.
Returns -1 if the connection failed to close
properly, or 0 otherwise.
*************************************************/

int
SocketClient::setUsingMulticast(int on_or_off)
{
	if (!((on_or_off == 0 && use_multicast == 0) ||
		(on_or_off != 0 && use_multicast != 0)))
	{
		if (current_socket->fd)
		{
			int retval;
			retval = closeConnection();
			use_multicast = on_or_off;
			return retval;
		}
		use_multicast = on_or_off;
	}
	return 0;
}

/**************************************************
SocketClient::getSuppressingMulticastLoopback

Returns 1 if the SocketClient is suppressing
loop back of multicast messages, 0 otherwise.
*************************************************/

int
SocketClient::getSuppressingMulticastLoopback()
{
	return (suppress_multicast_loop ? 1 : 0);
}

/**************************************************
SocketClient::setSuppressingMulticastLoopback

Returns 1 if the SocketClient is suppressing
loop back of multicast messages, 0 otherwise.
*************************************************/

int
SocketClient::setSuppressingMulticastLoopback(int on_or_off)
{
	if (!use_multicast)
	{
		if (!quiet)
			cerr << "SocketClient::setSuppressingMulticastLoopback: not in multicast mode" << endl;
		return -1;
	}

	if ((on_or_off == 0 && suppress_multicast_loop == 0) ||
		(on_or_off != 0 && suppress_multicast_loop != 0))
		return 0;

	if (current_socket->fd)
	{
		int retval;
		if (on_or_off == 0)
			retval = allowMulticastLoopback();
		else
			retval = suppressMulticastLoopback();
		if (!retval)
			suppress_multicast_loop = on_or_off;
		return retval;
	}

	suppress_multicast_loop = on_or_off;
	return 0;
}

/**************************************************
SocketClient::getPort

Returns the current port on which the 
SocketClient is operating.
*************************************************/

unsigned short
SocketClient::getPort()
{
	return port;
}

/**************************************************
SocketClient::setPort

Sets the port on which the SocketClient is 
operating. If a connection has been established
with a server, this connection is broken.
Returns MSG_NO_ERROR if the connection could
be closed, if necessary, and the port changed.

Errors:

*************************************************/

int
SocketClient::setPort(unsigned short new_port)
{
	int ret;

	if (current_socket->fd)
	{
		// need to close the current socket first

		if ((ret = closeConnection()) != MSG_NO_ERROR)
		{
			return ret;
		}
	}

	port = new_port;
	global_message = MSG_NO_ERROR;
	return global_message;
}

/**************************************************
SocketClient::setFDSet

Sets a user's passed in fd_set with the connected
socket of the Socket Client. Used to support an
external select() over several SocketMans or 
SocketClients; also used in selectOnSocket,
below.
*************************************************/

void
SocketClient::setFDSet(fd_set *fds)
{
	if (current_socket->fd)
		FD_SET(current_socket->fd, fds);
}

/**************************************************
SocketClient::readyToRead

Calling function passes in an fd_set which is
assumed to contain the result of a select()
prepared by setFDSet() (and possibly other
functions as well), above. Returns 
MSG_SOCKET_READY_TO_READ if the connected
socket is ready to read, or 
MSG_SOCKET_NOT_READY_TO_READ if it is not.

Errors:
ERR_CURRENT_SOCKET_NOT_OPEN
*************************************************/

int
SocketClient::readyToRead(fd_set *readfds)
{
	if (current_socket->fd)
	{
		if (FD_ISSET(current_socket->fd, readfds))
		{
			global_message = MSG_SOCKET_READY_TO_READ;
			return global_message;
		}

		global_message = MSG_SOCKET_NOT_READY_TO_READ;
		return global_message;
	}
	global_message = ERR_CURRENT_SOCKET_NOT_OPEN;
	return global_message;
}


/**************************************************
SocketClient::selectOnSocket

Executes a blocking select on the socket.
Returns MSG_SOCKET_READY_TO_READ when the socket
becomes readable. Returns ERR_DURING_SELECT if
select fails. Returns ERR_DURING_SELECT if select
returns and socket is not ready to read.
*************************************************/

int
SocketClient::selectOnSocket()
{
	fd_set readfds;
	int width;
	int result;

	// find the maximum number of open file descriptors
#if (!defined(__hpux) && !defined(WIN32))
	width = getdtablesize();
#else
	width = FD_SETSIZE;
#endif

	FD_ZERO(&readfds);

	FD_SET(current_socket->fd, &readfds);

#ifndef __hpux
	result = select(width, &readfds, NULL, NULL, 0);
#else
	result = select(width, (int *) &readfds, NULL, NULL, 0);
#endif  

	if (result < 0)
	{
		global_message = ERR_DURING_SELECT;
		return global_message;
	}
	else
	{
		if (FD_ISSET(current_socket->fd, &readfds))
		{
			global_message = MSG_SOCKET_READY_TO_READ;
			return global_message;
		}
		return ERR_DURING_SELECT;
	}
}

/**************************************************
SocketClient::connectAsTunnel         *** PRIVATE

Provides support for using two SocketClients to
make a multicast tunnel between subnets. Used in
the companion Tunnel class.
*************************************************/

int
SocketClient::connectAsTunnel(char *remote_host_name)
{
	struct sockaddr_in remote_host;
	struct hostent     *host_info;

	if (current_socket->fd)
	{
		if (!quiet)
			cerr << "SocketClient::connectAsTunnel: already connected" << endl;
		global_message = ERR_ALREADY_CONNECTED_TO_SERVER;
		return global_message;
	}

#ifndef SC_TEST_DEBUG
	if (current_socket->fd > 0)
	{
		if (!quiet)
		{
			cerr << "SocketClient::connectAsTunnel: already connected" << endl;
		}
		global_message = ERR_ALREADY_CONNECTED_TO_SERVER;
		return global_message;
	}
#endif

	host_info = (struct hostent *)gethostbyname((char *)remote_host_name);
	if (host_info == NULL)
	{  
		global_message = ERR_UNKNOWN_HOST;
		return global_message;
	}

	memset((char *)&remote_host, 0, sizeof(remote_host));
	memcpy((char *)&(remote_host.sin_addr), host_info->h_addr, host_info->h_length);
	remote_host.sin_family = host_info->h_addrtype;
	remote_host.sin_port = htons(port);

	use_udp = 1;

	int udp_sock;
	udp_sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (udp_sock == -1)
	{
		if (!quiet)
			cerr << "SocketClient::connectAsTunnel: error creating UDP socket" << endl;
		global_message = ERR_UDP_CREATING_SOCKET;
		return global_message;
	}

	// get our name so we can bind the
	// socket to our local port

	char                myname[SB_MAXHOSTNAME];
	int                 client_len;
	struct hostent      *my_info;
	struct sockaddr_in  client_info;

	client_len = sizeof(client_info);

	gethostname(myname, SB_MAXHOSTNAME);

	my_info = (struct hostent *)gethostbyname(myname);
	if (my_info == NULL)
	{  
		if (!quiet)
			cerr << "I don't know my own name: \"" << myname << "\"" << endl;
		global_message = ERR_UNKNOWN_HOST;
		return global_message;
	}

	memset((char *)&client_info, 0, client_len);
	memcpy((char *)&(client_info.sin_addr),
		my_info->h_addr,
		my_info->h_length);
	client_info.sin_family = my_info->h_addrtype;
	client_info.sin_port = htons(port);   // using same port number in UDP space
	// that we would use in TCP space

	// bind the UDP socket to our local port

	if (bind(udp_sock, (struct sockaddr *) &client_info, client_len) < 0)
	{  
		closesocket(udp_sock);
		if (!quiet)
			cerr << "SocketClient::connectAsTunnel: Error binding UDP socket to port" << endl;
		global_message = ERR_UDP_BINDING_SOCKET;
		return global_message;
	}

	if (connect(udp_sock, (struct sockaddr *)&remote_host, sizeof(remote_host)) != 0)
	{
		if (!quiet)
			cerr << "SocketClient::connectAsTunnel: UDP connect failed" << endl;
		global_message = ERR_UDP_CONNECTING_SOCKET;
		return global_message;
	}

	// enable non blocking IO for the UDP socket

	current_socket->fd = udp_sock;
#ifndef WIN32
	int flags;
	flags = fcntl(current_socket->fd, F_GETFL, 0);
	flags |= O_NDELAY;
	fcntl(current_socket->fd, F_SETFL, flags);
#else
	unsigned long nb_arg = 1;
	ioctlsocket(current_socket->fd, FIONBIO, &nb_arg);
#endif

	global_message = MSG_NO_ERROR;
	return global_message;
}
